<!DOCTYPE html>
<!--
Copyright 2016 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/core/test_utils.html">
<link rel="import" href="/tracing/model/memory_dump_test_utils.html">

<script>
'use strict';

tr.b.unittest.testSuite(function() {
  const VMRegion = tr.model.VMRegion;
  const VMRegionClassificationNode = tr.model.VMRegionClassificationNode;
  const checkVMRegions = tr.model.MemoryDumpTestUtils.checkVMRegions;

  function checkProtectionFlagsToString(protectionFlags, expectedString) {
    const vmRegion = VMRegion.fromDict({
      startAddress: 256,
      sizeInBytes: 336,
      protectionFlags,
      mappedFile: '[stack:20310]',
      byteStats: {
        privateDirtyResident: 96,
        swapped: 144,
        proportionalResident: 158
      }
    });
    assert.strictEqual(vmRegion.protectionFlagsToString, expectedString);
  }

  const TEST_RULES = {
    name: 'Root',
    children: [
      {
        name: 'Words',
        file: /^[a-zA-Z]/,
        children: [
          {
            name: 'A-D',
            file: /^[a-dA-D]/
          },
          {
            name: 'E-H',
            file: /^[e-hE-H]/
          }
        ]
      },
      {
        name: 'Digits',
        file: /\d$/,
        children: []
      }
    ]
  };

  // Constant representing the expectation that the children of a
  // VMRegionClassificationNode have not been built yet.
  const CHILDREN_NOT_BUILT_YET = {};

  function checkTree(node, expectedStructure) {
    assert.strictEqual(node.title, expectedStructure.title);
    assert.strictEqual(node.hasRegions, expectedStructure.hasRegions);
    assert.strictEqual(node.sizeInBytes, expectedStructure.sizeInBytes);
    assert.deepEqual(node.byteStats, expectedStructure.byteStats || {});
    assert.strictEqual(node.isLeafNode, expectedStructure.isLeafNode);

    const actualRegions = node.regions;
    const expectedRegions = expectedStructure.regions;
    if (expectedRegions === undefined) {
      assert.isUndefined(actualRegions);
    } else {
      assert.instanceOf(actualRegions, Array);
      checkVMRegions(actualRegions, expectedRegions);
    }

    const expectedChildren = expectedStructure.children;
    if (expectedChildren === CHILDREN_NOT_BUILT_YET) {
      assert.isUndefined(node.children_);
    } else if (expectedChildren === undefined) {
      assert.isUndefined(node.children);
    } else {
      const actualChildrenMap = new Map();
      node.children.forEach(function(childNode) {
        actualChildrenMap.set(childNode.title, childNode);
      });
      const expectedChildrenMap = new Map();
      expectedChildren.forEach(function(childNode) {
        expectedChildrenMap.set(childNode.title, childNode);
      });
      assert.strictEqual(actualChildrenMap.size, expectedChildrenMap.size);
      for (const title of expectedChildrenMap.keys()) {
        checkTree(actualChildrenMap.get(title),
            expectedChildrenMap.get(title));
      }
    }
  }

  function checkClassificationRules(mappedFile, expectedPath) {
    const region = VMRegion.fromDict({
      mappedFile,
      sizeInBytes: 16,
      byteStats: {
        privateDirtyResident: 7
      }
    });
    let node = VMRegionClassificationNode.fromRegions([region]);
    for (const title of expectedPath) {
      node = node.children.find(c => c.title === title);
    }
    assert.deepEqual(node.regions, [region]);
  }

  test('vmRegion_protectionFlagsToString', function() {
    checkProtectionFlagsToString(undefined, undefined);
    checkProtectionFlagsToString(0, '---p');
    checkProtectionFlagsToString(VMRegion.PROTECTION_FLAG_READ, 'r--p');
    checkProtectionFlagsToString(
        VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_MAYSHARE,
        'r--s');
    checkProtectionFlagsToString(
        VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_EXECUTE,
        'r-xp');
    checkProtectionFlagsToString(
        VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_WRITE,
        'rw-p');
    checkProtectionFlagsToString(
        VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_WRITE |
            VMRegion.PROTECTION_FLAG_EXECUTE,
        'rwxp');
    checkProtectionFlagsToString(
        VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_WRITE |
            VMRegion.PROTECTION_FLAG_MAYSHARE,
        'rw-s');
    checkProtectionFlagsToString(
        VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_EXECUTE |
            VMRegion.PROTECTION_FLAG_MAYSHARE,
        'r-xs');
    checkProtectionFlagsToString(
        VMRegion.PROTECTION_FLAG_READ | VMRegion.PROTECTION_FLAG_WRITE |
            VMRegion.PROTECTION_FLAG_EXECUTE |
            VMRegion.PROTECTION_FLAG_MAYSHARE,
        'rwxs');
  });

  // The add(After|Before)Build tests below check that the classification tree
  // has the correct structure regardless of the ordering of adding regions and
  // the lazy construction.

  test('vmRegionClassificationNode_constructor_addAfterBuild', function() {
    const rootNode = new VMRegionClassificationNode(TEST_RULES);

    // Check the root node and verify that the full tree structure has *not*
    // been constructed yet.
    checkTree(rootNode, {
      title: 'Root',
      hasRegions: false,
      isLeafNode: false,
      children: CHILDREN_NOT_BUILT_YET
    });

    // Reading the children of the root node *should* trigger building the
    // full tree.
    checkTree(rootNode, {
      title: 'Root',
      hasRegions: false,
      isLeafNode: false,
      children: [
        {
          title: 'Words',
          hasRegions: false,
          isLeafNode: false,
          children: [
            {
              title: 'A-D',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            },
            {
              title: 'E-H',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            }
          ]
        },
        {
          title: 'Digits',
          hasRegions: false,
          isLeafNode: true,
          regions: []
        }
      ]
    });

    // Add VM regions to the tree *after* it has been fully built.
    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: 'W2',  // Root/Words/Other.
      sizeInBytes: 16,
      byteStats: {
        proportionalResident: 32,
        swapped: 64
      }
    }));
    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: '__42',  // Root/Digits.
      byteStats: {
        proportionalResident: 33,
        privateDirtyResident: 77
      }
    }));
    checkTree(rootNode, {
      title: 'Root',
      hasRegions: true,
      sizeInBytes: 16,
      byteStats: {
        proportionalResident: 32 + 33,
        privateDirtyResident: 77,
        swapped: 64
      },
      isLeafNode: false,
      children: [
        {
          title: 'Words',
          hasRegions: true,
          sizeInBytes: 16,
          byteStats: {
            proportionalResident: 32,
            swapped: 64
          },
          isLeafNode: false,
          children: [
            {
              title: 'A-D',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            },
            {
              title: 'E-H',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            },
            {
              title: 'Other',
              hasRegions: true,
              sizeInBytes: 16,
              byteStats: {
                proportionalResident: 32,
                swapped: 64
              },
              isLeafNode: true,
              regions: [
                {
                  mappedFile: 'W2',
                  sizeInBytes: 16,
                  byteStats: {
                    proportionalResident: 32,
                    swapped: 64
                  }
                }
              ]
            }
          ]
        },
        {
          title: 'Digits',
          hasRegions: true,
          byteStats: {
            proportionalResident: 33,
            privateDirtyResident: 77
          },
          isLeafNode: true,
          regions: [
            {
              mappedFile: '__42',
              byteStats: {
                proportionalResident: 33,
                privateDirtyResident: 77
              }
            }
          ]
        }
      ]
    });
  });

  test('vmRegionClassificationNode_constructor_addBeforeBuild', function() {
    const rootNode = new VMRegionClassificationNode(TEST_RULES);

    // Add regions to the tree *before* it has been fully built. This should
    // *not* trigger building the full tree (but the total sizeInBytes and
    // byteStats should be updated accordingly).
    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: '__42',  // Root/Digits.
      byteStats: {
        proportionalResident: 33,
        privateDirtyResident: 77
      }
    }));
    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: 'W2',  // Root/Words/Other.
      sizeInBytes: 16,
      byteStats: {
        proportionalResident: 32,
        swapped: 64
      }
    }));
    checkTree(rootNode, {
      title: 'Root',
      hasRegions: true,
      sizeInBytes: 16,
      byteStats: {
        proportionalResident: 32 + 33,
        privateDirtyResident: 77,
        swapped: 64
      },
      isLeafNode: false,
      children: CHILDREN_NOT_BUILT_YET
    });

    // Reading the children of the root node should trigger building the full
    // tree.
    checkTree(rootNode, {
      title: 'Root',
      hasRegions: true,
      sizeInBytes: 16,
      byteStats: {
        proportionalResident: 32 + 33,
        privateDirtyResident: 77,
        swapped: 64
      },
      isLeafNode: false,
      children: [
        {
          title: 'Words',
          hasRegions: true,
          sizeInBytes: 16,
          byteStats: {
            proportionalResident: 32,
            swapped: 64
          },
          isLeafNode: false,
          children: [
            {
              title: 'A-D',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            },
            {
              title: 'E-H',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            },
            {
              title: 'Other',
              hasRegions: true,
              sizeInBytes: 16,
              byteStats: {
                proportionalResident: 32,
                swapped: 64
              },
              isLeafNode: true,
              regions: [
                {
                  mappedFile: 'W2',
                  sizeInBytes: 16,
                  byteStats: {
                    proportionalResident: 32,
                    swapped: 64
                  }
                }
              ]
            }
          ]
        },
        {
          title: 'Digits',
          hasRegions: true,
          byteStats: {
            proportionalResident: 33,
            privateDirtyResident: 77
          },
          isLeafNode: true,
          regions: [
            {
              mappedFile: '__42',
              byteStats: {
                proportionalResident: 33,
                privateDirtyResident: 77
              }
            }
          ]
        }
      ]
    });

    // Add more VM regions *after* the tree has been fully built.
    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: '%invalid%',  // Root/Other.
      sizeInBytes: 123
    }));
    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: '__43',  // Root/Digits.
      byteStats: {
        swapped: 19
      }
    }));
    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: 'free',  // Root/Words/E-H.
      sizeInBytes: undefined
    }));
    checkTree(rootNode, {
      title: 'Root',
      hasRegions: true,
      sizeInBytes: 16 + 123,
      byteStats: {
        proportionalResident: 32 + 33,
        privateDirtyResident: 77,
        swapped: 64 + 19,
      },
      isLeafNode: false,
      children: [
        {
          title: 'Words',
          hasRegions: true,
          sizeInBytes: 16,
          byteStats: {
            proportionalResident: 32,
            swapped: 64
          },
          isLeafNode: false,
          children: [
            {
              title: 'A-D',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            },
            {
              title: 'E-H',
              hasRegions: true,
              isLeafNode: true,
              regions: [
                {
                  mappedFile: 'free'
                }
              ]
            },
            {
              title: 'Other',
              hasRegions: true,
              sizeInBytes: 16,
              byteStats: {
                proportionalResident: 32,
                swapped: 64
              },
              isLeafNode: true,
              regions: [
                {
                  mappedFile: 'W2',
                  sizeInBytes: 16,
                  byteStats: {
                    proportionalResident: 32,
                    swapped: 64
                  }
                }
              ]
            }
          ]
        },
        {
          title: 'Digits',
          hasRegions: true,
          byteStats: {
            proportionalResident: 33,
            privateDirtyResident: 77,
            swapped: 19
          },
          isLeafNode: true,
          regions: [
            {
              mappedFile: '__42',
              byteStats: {
                proportionalResident: 33,
                privateDirtyResident: 77
              }
            },
            {
              mappedFile: '__43',
              byteStats: {
                swapped: 19
              }
            }
          ]
        },
        {
          title: 'Other',
          hasRegions: true,
          sizeInBytes: 123,
          isLeafNode: true,
          regions: [
            {
              mappedFile: '%invalid%',
              sizeInBytes: 123
            }
          ]
        }
      ]
    });
  });

  test('vmRegionClassificationNode_fromRegions_addAfterBuild', function() {
    // Construct the root node from a list of regions. This should *not*
    // trigger building the full tree (but the total sizeInBytes and byteStats
    // should be updated accordingly).
    const rootNode = VMRegionClassificationNode.fromRegions([
      VMRegion.fromDict({
        mappedFile: '__42',  // Root/Digits.
        byteStats: {
          proportionalResident: 33,
          privateDirtyResident: 77
        }
      }),
      VMRegion.fromDict({
        mappedFile: '__43',  // Root/Digits.
        byteStats: {
          swapped: 19
        }
      })
    ], TEST_RULES);
    checkTree(rootNode, {
      title: 'Root',
      hasRegions: true,
      byteStats: {
        proportionalResident: 33,
        privateDirtyResident: 77,
        swapped: 19
      },
      isLeafNode: false,
      children: CHILDREN_NOT_BUILT_YET
    });

    // Reading the children of the root node should trigger building the full
    // tree.
    checkTree(rootNode, {
      title: 'Root',
      hasRegions: true,
      byteStats: {
        proportionalResident: 33,
        privateDirtyResident: 77,
        swapped: 19
      },
      isLeafNode: false,
      children: [
        {
          title: 'Words',
          hasRegions: false,
          isLeafNode: false,
          children: [
            {
              title: 'A-D',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            },
            {
              title: 'E-H',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            }
          ]
        },
        {
          title: 'Digits',
          hasRegions: true,
          byteStats: {
            proportionalResident: 33,
            privateDirtyResident: 77,
            swapped: 19
          },
          isLeafNode: true,
          regions: [
            {
              mappedFile: '__42',
              byteStats: {
                proportionalResident: 33,
                privateDirtyResident: 77
              }
            },
            {
              mappedFile: '__43',
              byteStats: {
                swapped: 19
              }
            }
          ]
        }
      ]
    });

    // Add more VM regions *after* the tree has been fully built.
    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: 'W2',  // Root/Words/Other.
      sizeInBytes: 16,
      byteStats: {
        proportionalResident: 32,
        swapped: 64
      }
    }));
    checkTree(rootNode, {
      title: 'Root',
      hasRegions: true,
      sizeInBytes: 16,
      byteStats: {
        proportionalResident: 32 + 33,
        privateDirtyResident: 77,
        swapped: 19 + 64,
      },
      isLeafNode: false,
      children: [
        {
          title: 'Words',
          hasRegions: true,
          sizeInBytes: 16,
          byteStats: {
            proportionalResident: 32,
            swapped: 64
          },
          isLeafNode: false,
          children: [
            {
              title: 'A-D',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            },
            {
              title: 'E-H',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            },
            {
              title: 'Other',
              hasRegions: true,
              sizeInBytes: 16,
              byteStats: {
                proportionalResident: 32,
                swapped: 64
              },
              isLeafNode: true,
              regions: [
                {
                  mappedFile: 'W2',
                  sizeInBytes: 16,
                  byteStats: {
                    proportionalResident: 32,
                    swapped: 64
                  }
                }
              ]
            }
          ]
        },
        {
          title: 'Digits',
          hasRegions: true,
          byteStats: {
            proportionalResident: 33,
            privateDirtyResident: 77,
            swapped: 19
          },
          isLeafNode: true,
          regions: [
            {
              mappedFile: '__42',
              byteStats: {
                proportionalResident: 33,
                privateDirtyResident: 77
              }
            },
            {
              mappedFile: '__43',
              byteStats: {
                swapped: 19
              }
            }
          ]
        }
      ]
    });
  });

  test('vmRegionClassificationNode_fromRegions_addBeforeBuild', function() {
    // Construct the root node from a list of regions and then add another
    // region. This should *not* trigger building the full tree (but the total
    // sizeInBytes and byteStats should be updated accordingly).
    const rootNode = VMRegionClassificationNode.fromRegions([
      VMRegion.fromDict({
        mappedFile: '__42',  // Root/Digits.
        byteStats: {
          proportionalResident: 33,
          privateDirtyResident: 77
        }
      }),
      VMRegion.fromDict({
        mappedFile: '__43',  // Root/Digits.
        byteStats: {
          swapped: 19
        }
      })
    ], TEST_RULES);
    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: '__42',  // Root/Digits.
      startAddress: 2048,  // Necessary to distinguish from the first region.
      sizeInBytes: 1000,
      byteStats: {
        privateDirtyResident: 500
      }
    }));
    checkTree(rootNode, {
      title: 'Root',
      hasRegions: true,
      sizeInBytes: 1000,
      byteStats: {
        proportionalResident: 33,
        privateDirtyResident: 77 + 500,
        swapped: 19
      },
      isLeafNode: false,
      children: CHILDREN_NOT_BUILT_YET
    });

    // Reading the children of the root node should trigger building the full
    // tree.
    checkTree(rootNode, {
      title: 'Root',
      hasRegions: true,
      sizeInBytes: 1000,
      byteStats: {
        proportionalResident: 33,
        privateDirtyResident: 77 + 500,
        swapped: 19
      },
      isLeafNode: false,
      children: [
        {
          title: 'Words',
          hasRegions: false,
          isLeafNode: false,
          children: [
            {
              title: 'A-D',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            },
            {
              title: 'E-H',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            }
          ]
        },
        {
          title: 'Digits',
          hasRegions: true,
          sizeInBytes: 1000,
          byteStats: {
            proportionalResident: 33,
            privateDirtyResident: 77 + 500,
            swapped: 19
          },
          isLeafNode: true,
          regions: [
            {
              mappedFile: '__42',
              byteStats: {
                proportionalResident: 33,
                privateDirtyResident: 77
              }
            },
            {
              mappedFile: '__43',
              byteStats: {
                swapped: 19
              }
            },
            {
              mappedFile: '__42',
              startAddress: 2048,
              sizeInBytes: 1000,
              byteStats: {
                privateDirtyResident: 500
              }
            }
          ]
        }
      ]
    });

    // Add more VM regions *after* the tree has been fully built.
    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: 'W2',  // Root/Words/Other.
      sizeInBytes: 16,
      byteStats: {
        proportionalResident: 32,
        swapped: 64
      }
    }));
    checkTree(rootNode, {
      title: 'Root',
      hasRegions: true,
      sizeInBytes: 1000 + 16,
      byteStats: {
        proportionalResident: 32 + 33,
        privateDirtyResident: 500 + 77,
        swapped: 19 + 64,
      },
      isLeafNode: false,
      children: [
        {
          title: 'Words',
          hasRegions: true,
          sizeInBytes: 16,
          byteStats: {
            proportionalResident: 32,
            swapped: 64
          },
          isLeafNode: false,
          children: [
            {
              title: 'A-D',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            },
            {
              title: 'E-H',
              hasRegions: false,
              isLeafNode: true,
              regions: []
            },
            {
              title: 'Other',
              hasRegions: true,
              sizeInBytes: 16,
              byteStats: {
                proportionalResident: 32,
                swapped: 64
              },
              isLeafNode: true,
              regions: [
                {
                  mappedFile: 'W2',
                  sizeInBytes: 16,
                  byteStats: {
                    proportionalResident: 32,
                    swapped: 64
                  }
                }
              ]
            }
          ]
        },
        {
          title: 'Digits',
          hasRegions: true,
          sizeInBytes: 1000,
          byteStats: {
            proportionalResident: 33,
            privateDirtyResident: 500 + 77,
            swapped: 19
          },
          isLeafNode: true,
          regions: [
            {
              mappedFile: '__42',
              byteStats: {
                proportionalResident: 33,
                privateDirtyResident: 77
              }
            },
            {
              mappedFile: '__43',
              byteStats: {
                swapped: 19
              }
            },
            {
              mappedFile: '__42',
              startAddress: 2048,
              sizeInBytes: 1000,
              byteStats: {
                privateDirtyResident: 500
              }
            }
          ]
        }
      ]
    });
  });

  test('vmRegionClassificationNode_someRegion', function() {
    const rootNode = new VMRegionClassificationNode(TEST_RULES);

    // There are no regions in the tree, so the method should always return
    // false.
    assert.isFalse(rootNode.someRegion(function(region) {
      throw new Error('There are no regions in the tree!!!');
    }));

    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: 'W2',  // Root/Words/Other.
      sizeInBytes: 16,
      byteStats: {
        proportionalResident: 32,
        swapped: 64
      }
    }));
    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: '__42',  // Root/Digits.
      byteStats: {
        proportionalResident: 33,
        privateDirtyResident: 77
      }
    }));
    rootNode.addRegion(VMRegion.fromDict({
      mappedFile: '__43',  // Root/Digits.
      byteStats: {
        proportionalResident: 33,
        privateDirtyResident: 77
      }
    }));

    function checkSomeRegion() {
      // Find the order in which the regions are traversed and checked that all
      // regions were visited.
      const visitedRegionMappedFiles = [];
      assert.isFalse(rootNode.someRegion(function(region) {
        visitedRegionMappedFiles.push(region.mappedFile);
        return false;
      }));
      assert.lengthOf(visitedRegionMappedFiles, 3);
      assert.sameMembers(visitedRegionMappedFiles, ['W2', '__42', '__43']);

      // Assuming the traversal order is deterministic, we check that once the
      // callback returns true, no further regions are visited.
      visitedRegionMappedFiles.forEach(
          function(mappedFileToMatch, index) {
            const visitedRegionMappedFiles2 = [];
            assert.isTrue(rootNode.someRegion(function(region) {
              this.files.push(region.mappedFile);
              return region.mappedFile === mappedFileToMatch;
            }, { files: visitedRegionMappedFiles2 } /* opt_this */));
            assert.deepEqual(visitedRegionMappedFiles2,
                visitedRegionMappedFiles.slice(0, index + 1));
          });
    }

    // Before lazy construction (single node with a flat list of regions).
    checkSomeRegion();
    assert.isUndefined(rootNode.children_);

    // After lazy construction (tree of nodes with lists of regions).
    assert.isDefined(rootNode.children);  // Force building the tree.
    assert.isDefined(rootNode.children_);
    checkSomeRegion();
  });

  test('vmRegionClassificationNode_libraryMemory', function() {
    const regions = [
      VMRegion.fromDict({
        sizeInBytes: 20,
        protectionFlags: VMRegion.PROTECTION_FLAG_READ,
        mappedFile: '[stack:1234]',
        byteStats: {
          privateDirtyResident: 100,
          proportionalResident: 124
        }
      }),
      VMRegion.fromDict({
        sizeInBytes: 500000,
        protectionFlags: VMRegion.PROTECTION_FLAG_READ,
        mappedFile: '/data/app/com.google.chrome/base.apk',
        byteStats: {
          privateCleanResident: 100000,
          proportionalResident: 124000
        }
      }),
      VMRegion.fromDict({
        sizeInBytes: 1000,
        protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
                          VMRegion.PROTECTION_FLAG_EXECUTE),
        mappedFile: '/data/app/com.google.chrome/base.apk',
        byteStats: {
          privateCleanResident: 100,
          proportionalResident: 124
        }
      }),
      VMRegion.fromDict({
        sizeInBytes: 300,
        protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
                          VMRegion.PROTECTION_FLAG_EXECUTE),
        mappedFile: '/data/app/com.google.chrome/base.apk',
        byteStats: {
          proportionalResident: 58,
        }
      }),
      VMRegion.fromDict({
        sizeInBytes: 400,
        protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
                          VMRegion.PROTECTION_FLAG_EXECUTE),
        mappedFile: '/data/app/com.google.chrome/base.apk',
        byteStats: {
          privateCleanResident: 76,
        }
      }),
      VMRegion.fromDict({
        sizeInBytes: 50,
        protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
                          VMRegion.PROTECTION_FLAG_EXECUTE),
        mappedFile: '/data/app/com.google.chrome/base.apk',
        byteStats: {
          privateCleanResident: 8,
          proportionalResident: 24,
          sharedCleanResident: 10,
        }
      })];
    const node = VMRegionClassificationNode.fromRegions(regions);
    assert.strictEqual(node.sizeInBytes, 20 + 500000 + 1000 + 300 + 400 + 50);
    assert.strictEqual(node.nativeLibrarySizeInBytes,
        1000 + 300 + 400 + 50);

    assert.deepEqual(node.byteStats, {
      proportionalResident: 124 + 124000 + 124 + 58 + 24,
      privateDirtyResident: 100,
      privateCleanResident: 100000 + 100 + 76 + 8,
      sharedCleanResident: 10,
      nativeLibraryPrivateCleanResident: 100 + 76 + 8,
      nativeLibrarySharedCleanResident: 10,
      nativeLibraryProportionalResident: 124 + 58 + 24,
    });
  });

  test('vmRegionClassificationNode_javaBaseMemory', function() {
    const regions = [
      VMRegion.fromDict({
        sizeInBytes: 20,
        protectionFlags: VMRegion.PROTECTION_FLAG_READ,
        mappedFile: '[stack:1234]',
        byteStats: {
          privateDirtyResident: 100,
          proportionalResident: 124
        }
      }),
      VMRegion.fromDict({
        sizeInBytes: 500000,
        protectionFlags: VMRegion.PROTECTION_FLAG_READ,
        mappedFile: '/data/app/com.google.chrome/oat/arm/base.vdex',
        byteStats: {
          privateCleanResident: 100000,
          proportionalResident: 124000
        }
      }),
      VMRegion.fromDict({
        sizeInBytes: 1000,
        protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
                          VMRegion.PROTECTION_FLAG_EXECUTE),
        mappedFile: '/data/app/com.google.chrome/some/other.odex',
        byteStats: {
          privateCleanResident: 100,
          proportionalResident: 124
        }
      }),
      VMRegion.fromDict({
        sizeInBytes: 300,
        protectionFlags: (VMRegion.PROTECTION_FLAG_READ),
        mappedFile: '/data/app/com.google.chrome/oat/arm/base.odex',
        byteStats: {
          proportionalResident: 58,
        }
      }),
      VMRegion.fromDict({
        sizeInBytes: 400,
        protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
                          VMRegion.PROTECTION_FLAG_EXECUTE),
        mappedFile: '/data/app/com.google.chrome/base.vdex',
        byteStats: {
          privateCleanResident: 76,
          privateDirtyResident: 17,
        }
      }),
      VMRegion.fromDict({
        sizeInBytes: 50,
        protectionFlags: (VMRegion.PROTECTION_FLAG_READ |
                          VMRegion.PROTECTION_FLAG_EXECUTE),
        mappedFile: '/data/app/com.google.chrome/another/path/base.odex',
        byteStats: {
          privateCleanResident: 8,
          proportionalResident: 24,
          sharedCleanResident: 10,
        }
      })];
    const node = VMRegionClassificationNode.fromRegions(regions);
    assert.strictEqual(node.sizeInBytes, 20 + 500000 + 1000 + 300 + 400 + 50);

    assert.deepEqual(node.byteStats, {
      proportionalResident: 124 + 124000 + 124 + 58 + 24,
      privateDirtyResident: 100 + 17,
      privateCleanResident: 100000 + 100 + 76 + 8,
      sharedCleanResident: 10,
      javaBasePss: 124000 + 58 + 24,
      javaBaseCleanResident: 100000 + 76 + 8 + 10,
    });
  });

  test('classificationRules', function() {
    checkClassificationRules('/dev/ashmem/dalvik-main space (deleted)',
        ['Android', 'Java runtime', 'Spaces', 'Normal']);
    checkClassificationRules('/dev/ashmem/dalvik-non moving space',
        ['Android', 'Java runtime', 'Spaces', 'Non-moving']);
    checkClassificationRules('/dev/ashmem/dalvik-zygote space (deleted)',
        ['Android', 'Java runtime', 'Spaces', 'Zygote']);
    checkClassificationRules('/dev/ashmem/dalvik-allocation stack (deleted)',
        ['Android', 'Java runtime', 'Accounting']);
    checkClassificationRules(
        '/dev/ashmem/dalvik-allocspace main rosalloc space 1 live-bitmap 2',
        ['Android', 'Java runtime', 'Accounting']);
    checkClassificationRules(
        '/dev/ashmem/dalvik-allocspace non moving space live-bitmap 4',
        ['Android', 'Java runtime', 'Accounting']);
    checkClassificationRules('/dev/ashmem/dalvik-allocspace zygote / ' +
        'non moving space live-bitmap 0 (deleted)',
    ['Android', 'Java runtime', 'Accounting']);
    checkClassificationRules('/dev/ashmem/dalvik-card table (deleted)',
        ['Android', 'Java runtime', 'Accounting']);
    checkClassificationRules('/dev/ashmem/dalvik-large live objects (deleted)',
        ['Android', 'Java runtime', 'Accounting']);
    checkClassificationRules('/dev/ashmem/dalvik-live stack (deleted)',
        ['Android', 'Java runtime', 'Accounting']);
    checkClassificationRules(
        '/dev/ashmem/dalvik-mark sweep sweep array free buffer (deleted)',
        ['Android', 'Java runtime', 'Accounting']);
    checkClassificationRules('/dev/ashmem/dalvik-rosalloc page map (deleted)',
        ['Android', 'Java runtime', 'Accounting']);
    checkClassificationRules('/dev/ashmem/dalvik-indirect ref table (deleted)',
        ['Android', 'Java runtime', 'Indirect Reference Table']);
    checkClassificationRules('/dev/ashmem/dalvik-LinearAlloc (deleted)',
        ['Android', 'Java runtime', 'Linear Alloc']);
    checkClassificationRules('/dev/ashmem/dalvik-jit-code-cache (deleted)',
        ['Android', 'Java runtime', 'Cache']);
    checkClassificationRules('/dev/ashmem/CursorWindow (deleted)',
        ['Android', 'Cursor']);
    checkClassificationRules('/dev/ashmem (deleted)', ['Android', 'Ashmem']);
    checkClassificationRules('/dev/ashmem/GFXStats-10082',
        ['Android', 'Ashmem']);

    checkClassificationRules('[stack:23164]', ['Stack']);
    checkClassificationRules('[stack]', ['Stack']);

    checkClassificationRules('[discounted tracing overhead]', ['Native heap']);
    checkClassificationRules('', ['Native heap']);
    checkClassificationRules('[heap]', ['Native heap']);
    checkClassificationRules('[anon:libc_malloc]', ['Native heap']);
    checkClassificationRules('[anon:thread signal stack]', ['Native heap']);
    checkClassificationRules('/dev/ashmem/libc malloc (deleted)',
        ['Native heap']);

    checkClassificationRules('/usr/lib/nvidia-340/libGL.so.331.79',
        ['Files', 'so']);
    checkClassificationRules('/usr/lib/x86_64-linux-gnu/libibus-1.0.so.5.0.505',
        ['Files', 'so']);
    checkClassificationRules('/data/data/com.google.android.apps.chrome/' +
        'app_chrome/RELRO:libchrome.so (deleted)', ['Files', 'so']);
    checkClassificationRules(
        '/usr/share/fonts/truetype/msttcorefonts/Times_New_Roman.ttf',
        ['Files', 'ttf']);
    checkClassificationRules(
        '/data/app/com.google.android.apps.chrome-2/base.apk',
        ['Files', 'apk']);
    checkClassificationRules(
        '/data/app/com.google.android.apps.chrome-2/lib/arm/libchrome.so',
        ['Files', 'so']);
    checkClassificationRules(
        '/data/app/com.google.android.apps.chrome-2/oat/arm/base.odex',
        ['Files', 'dex']);
    checkClassificationRules(
        '/data/dalvik-cache/arm/system@framework@boot.art', ['Files', 'art']);
    checkClassificationRules(
        '/data/dalvik-cache/arm/system@framework@boot.oat', ['Files', 'oat']);

    checkClassificationRules('/dev/nvidia0', ['Devices', 'GPU']);
    checkClassificationRules('/dev/kgsl-3d0', ['Devices', 'GPU']);
    checkClassificationRules('anon_inode:dmabuf', ['Devices', 'DMA']);
    checkClassificationRules('/dev/binder', ['Devices', 'Other']);

    checkClassificationRules('/src/out/Release/chrome', ['Other']);
    checkClassificationRules('/tmp/gluY4SVp (deleted)', ['Other']);
    checkClassificationRules('/src/out/Release/resources.pak', ['Other']);
    checkClassificationRules('[vdso]', ['Other']);
    checkClassificationRules('[vsyscall]', ['Other']);
    checkClassificationRules('[vectors]', ['Other']);
    checkClassificationRules('[vvar]', ['Other']);
  });
});
</script>
